package communications;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * The BluetoothHandler class is responsible for all interactions
 * between the application and the shoe device, using a virtual serial port.
 *
 * @author Robin Persson
 */

public class BluetoothHandler {
    private final int baudrate = 9600;
    private final String wantedPortName;

    SerialPort port = null;
    
	InputStream reader = null;
	PrintStream writer = null;
	
	private Logger logger = Logger.getLogger("communications.BluetoothHandler");
    
    public BluetoothHandler(String wantedPortName) throws PortInUseException, NoSuchPortException {
    	
    	this.wantedPortName = wantedPortName;
     
    	try {
    		initializeConnection();
		} catch (UnsupportedCommOperationException e) {
			close();
			logger.log(Level.SEVERE, null, e);
		} catch (IOException e) {
			close();
			logger.log(Level.SEVERE, null, e);
		}
    }
    
    private void initializeConnection() throws
    	NoSuchPortException,
    	PortInUseException,
    	UnsupportedCommOperationException,
    	IOException {
	    	CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(wantedPortName); 
		
	        port = (SerialPort) portId.open(
	            "Motivational Shoes", // Name of the application asking for the port 
	            10000   // Wait max. 10 sec. to acquire port
	        );
			logger.log(Level.INFO, "Connected to shoe device.");
			port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
	        port.setSerialPortParams(
				 baudrate,
				 SerialPort.DATABITS_8,
				 SerialPort.STOPBITS_1,
				 SerialPort.PARITY_NONE);
			
			reader = port.getInputStream();
			writer = new PrintStream(port.getOutputStream(), false);
    }
    
    public ArrayList<Long> getNewData() throws IOException {
    	this.sendCommand('a');
		
		//Returns 4xchar per timestamp; Repeat;
    	//'a' is always echoed, even if there is no data.
    	
    	return this.receiveData();
    }
	
	public int getVoltage() throws IOException{
		this.sendCommand('v');
		//Returns 1xchar
		return this.receiveByte();
	}
    
    private boolean hasData() throws IOException {
		InputStreamReader r = new InputStreamReader(reader);
    	return r.ready();
    }
    
    public void goodPerformance() {
		setLED(0,255,0);
    }
    
    public void beenAWhile() {
		setLED(255,0,0);
    }
	
	public void testLED(int r, int g, int b) {
		setLED(r, g, b);
	}
	
	private void setLED(int r, int g, int b) {
		if(r == 0) r = 1;
		if(g == 0) g = 1;
		if(b == 0) b = 1;
		
		sendCommand('c');
		sendCommand((char)r);
		sendCommand((char)g);
		sendCommand((char)b);
	}

    private void sendCommand(char c) {
    	writer.print(c);
    	writer.flush();
		logger.log(Level.INFO, "Sent command to shoe device: '"+c+"'");
    }
    
	public int receiveByte() throws IOException {
		logger.log(Level.INFO, "Waiting for data from shoe device...");
		
		while(!hasData()) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException ex) {
				logger.log(Level.SEVERE, null, ex);
			}
		}
		int j = reader.read();
		return j;
	}
	
	public ArrayList<Long> receiveData() throws IOException {
		ArrayList<Long> timestampList = new ArrayList<Long>();
		logger.log(Level.INFO, "Waiting for data from shoe device...");

		reader.read(); //read the echoed command first
		
		long timestamp = 0;
		long now = new Date().getTime();
		long temp = 0;
		boolean firstValue = true;
		while(true) {
			if(!hasData()) {
				//WAIT 1 SECOND TO RECEIVE DATA, OTHERWISE NO NEW DATA
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if(!hasData()) break;
			}
			long[] bs = new long[4];
			for(int i = 0; i < bs.length; i++) {
				temp = reader.read();
				bs[i] = temp;
			}
			
			long[] is = new long[4];
			timestamp = 0;
			for(int i = 0; i < 4; i++) {
				is[i] = bs[i] * (long)Math.pow(2, 8*(3-i));
				timestamp += is[i];
			}
			
			if(firstValue) {
				now -= timestamp;
				firstValue = false;
			}
			
			long realTimestamp = now + timestamp;
			timestampList.add(realTimestamp);

			
			if (timestampList.size() > 4)
			{
				int index = timestampList.size()-1;
				if (timestampList.get(index-1) > timestampList.get(index))
				{
					timestampList.set(index-1, (timestampList.get(index) - timestampList.get(index-2))/2 + timestampList.get(index-2));
				}
			}
			
			
		}
		String s = "";
		Iterator<Long> it = timestampList.iterator();
		while(it.hasNext())
		{
			s += (it.next() + " ");
		}
		return timestampList;
	}
	
	public void resetDevice() {
		try {
			this.sendCommand('C');
			this.receiveByte();
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		
		this.setLED(0, 64, 64);
		
		logger.log(Level.INFO, "Shoe device reset.");
	}
	
	public void close() {
		if (reader != null)
			try {
				reader.close();
			} catch (IOException e) {}
		if (writer != null) writer.close();
		if (port != null) port.close();
		logger.log(Level.INFO, "Closed shoe device connection.");
	}
}
